package org.gdstash.db;

import org.gdstash.description.BonusDetail;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;

public class DBSkillBonus implements Cloneable {
    public static final String TABLE_NAME_ITEM = "GD_ITEM_SKILLS";
    public static final String TABLE_NAME_ITEMSET = "GD_ITEMSET_SKILLS";
    public static final String TABLE_NAME_AFFIX = "GD_AFFIX_SKILLS";
    private static final String FIELD_ID_ITEM = "ITEM_ID";
    private static final String FIELD_ID_ITEMSET = "ITEMSET_ID";
    private static final String FIELD_ID_AFFIX = "AFFIX_ID";
    private static final int ROW_ITEM_ID = 1;
    private static final int ROW_BONUS_TYPE = 2;
    private static final int ROW_INDEX = 3;
    private static final int ROW_BONUS_ENTITY = 4;
    private static final int ROW_ENTITY_NAME = 5;
    private static final int ROW_MASTERY_ID = 6;
    public static final int TYPE_MASTERY = 1;
    public static final int TYPE_CLASS_SKILL = 2;
    public static final int TYPE_GRANTED_SKILL = 3;
    public static final int TYPE_PLUS_TO_ALL = 4;
    private String itemID;
    private int type;
    private int index;
    private String bonusEntity;
    private String entityName;
    private String masteryID;
    private List<DBSkillBonusLevel> levels;
    private int currBonus;
    private String masteryName;

    public DBSkillBonus() {
        this.itemID = null;
        this.type = 0;
        this.index = 0;
        this.bonusEntity = null;
        this.entityName = null;
        this.masteryID = null;
        this.masteryName = null;
        this.levels = new LinkedList<DBSkillBonusLevel>();
    }

    public DBSkillBonus(final int index, final int type, final String bonusEntity) {
        this.itemID = null;
        this.type = type;
        this.index = index;
        this.bonusEntity = bonusEntity;
        this.entityName = null;
        this.masteryID = null;
        this.masteryName = null;
        this.levels = new LinkedList<DBSkillBonusLevel>();
    }

    public DBSkillBonus(final int index, final int type) {
        this.itemID = null;
        this.type = type;
        this.index = index;
        this.bonusEntity = null;
        this.entityName = null;
        this.masteryID = null;
        this.masteryName = null;
        this.levels = new LinkedList<DBSkillBonusLevel>();
    }

    private DBSkillBonus(final DBSkillBonus bonus) {
        this.itemID = bonus.itemID;
        this.type = bonus.type;
        this.index = bonus.index;
        this.bonusEntity = bonus.bonusEntity;
        this.levels = bonus.levels;
        this.entityName = bonus.entityName;
        this.masteryID = bonus.masteryID;
        this.masteryName = bonus.masteryName;
        this.currBonus = bonus.currBonus;
    }

    public Object clone() {
        return new DBSkillBonus(this);
    }

    public String getID() {
        return this.itemID;
    }

    public void setID(final String itemID) {
        this.itemID = itemID;
    }

    public int getSkillBonusType() {
        return this.type;
    }

    public void setSkillBonusType(final int type) {
        this.type = type;
    }

    public int getIndex() {
        return this.index;
    }

    public void setIndex(final int index) {
        this.index = index;
    }

    public String getEntity() {
        return this.bonusEntity;
    }

    public void setEntity(final String bonusEntity) {
        this.bonusEntity = bonusEntity;
    }

    public String getEntityName() {
        return this.entityName;
    }

    public void setEntityName(final String entityName) {
        this.entityName = entityName;
    }

    public static void addEntity(final List<DBSkillBonus> list, final int bonusIndex, final int type, final String bonusEntity) {
        boolean found = false;
        for (final DBSkillBonus bonus : list) {
            if (bonus.getIndex() == bonusIndex && bonus.getSkillBonusType() == type) {
                bonus.setEntity(bonusEntity);
                found = true;
                break;
            }
        }
        if (!found) {
            final DBSkillBonus bonus2 = new DBSkillBonus(bonusIndex, type, bonusEntity);
            list.add(bonus2);
        }
    }

    public static void addValue(final List<DBSkillBonus> list, final int bonusIndex, final int type, final int bonusValue, final int valueIndex) {
        boolean found = false;
        for (final DBSkillBonus bonus : list) {
            if (bonus.getIndex() == bonusIndex && bonus.getSkillBonusType() == type) {
                bonus.levels.add(new DBSkillBonusLevel(bonusValue, valueIndex));
                bonus.currBonus = bonusValue;
                found = true;
                break;
            }
        }
        if (!found) {
            final DBSkillBonus bonus2 = new DBSkillBonus(bonusIndex, type);
            bonus2.levels.add(new DBSkillBonusLevel(bonusValue, valueIndex));
            bonus2.currBonus = bonusValue;
            list.add(bonus2);
        }
    }

    public String getMasteryName() {
        return this.masteryName;
    }

    public int getValue() {
        return this.currBonus;
    }

    public void setValue(final int value) {
        this.currBonus = value;
    }

    public int getValueForLevel(final int level) {
        int value = 0;
        for (final DBSkillBonusLevel bl : this.levels) {
            value = bl.getValue();
            if (bl.getLevel() == level) {
                break;
            }
        }
        return value;
    }

    public List<DBSkillBonusLevel> getLevelList() {
        return this.levels;
    }

    public static void createItemTable(final Connection conn) throws SQLException {
        createTable(conn, "GD_ITEM_SKILLS", "GD_ITEM_SKILL_LEVEL", "ITEM_ID");
    }

    public static void createItemSetTable(final Connection conn) throws SQLException {
        createTable(conn, "GD_ITEMSET_SKILLS", "GD_ITEMSET_SKILL_LEVEL", "ITEMSET_ID");
    }

    public static void createAffixTable(final Connection conn) throws SQLException {
        createTable(conn, "GD_AFFIX_SKILLS", "GD_AFFIX_SKILL_LEVEL", "AFFIX_ID");
    }

    private static void createTable(final Connection conn, final String tabName, final String tabNameLevel, final String idName) throws SQLException {
        final String dropTable = "DROP TABLE " + tabName;
        final String createTable = "CREATE TABLE " + tabName + " (" + idName + "    VARCHAR(" + "256" + ") NOT NULL, TYPE         INTEGER, INDEX        INTEGER, BONUS_ENTITY VARCHAR(" + "256" + "), ENTITY_NAME  VARCHAR(" + "256" + "), MASTERY_ID   VARCHAR(" + "256" + "), PRIMARY KEY (" + idName + ", TYPE, INDEX))";
        try (final Statement st = conn.createStatement()) {
            if (GDDBUtil.tableExists(conn, tabName)) {
                st.execute(dropTable);
            }
            st.execute(createTable);
            st.close();
            conn.commit();
            DBSkillBonusLevel.createTable(conn, tabNameLevel, idName);
        } catch (SQLException ex) {
            final Object[] args = {tabName};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
            GDMsgLogger.addError(msg);
            throw ex;
        }
    }

    public static void deleteItem(final Connection conn, final String id) throws SQLException {
        delete(conn, "GD_ITEM_SKILLS", "GD_ITEM_SKILL_LEVEL", "ITEM_ID", id);
    }

    public static void deleteItemSet(final Connection conn, final String id) throws SQLException {
        delete(conn, "GD_ITEMSET_SKILLS", "GD_ITEMSET_SKILL_LEVEL", "ITEMSET_ID", id);
    }

    public static void deleteAffix(final Connection conn, final String id) throws SQLException {
        delete(conn, "GD_AFFIX_SKILLS", "GD_AFFIX_SKILL_LEVEL", "AFFIX_ID", id);
    }

    private static void delete(final Connection conn, final String tabName, final String tabNameLevel, final String fieldName, final String itemID) throws SQLException {
        final String deleteEntry = "DELETE FROM " + tabName + " WHERE " + fieldName + " = ?";
        try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
            ps.setString(1, itemID);
            ps.executeUpdate();
            ps.close();
            DBSkillBonusLevel.delete(conn, tabNameLevel, fieldName, itemID);
        } catch (SQLException ex) {
            conn.rollback();
        }
    }

    public static void insertItem(final Connection conn, final String id, final List<DBSkillBonus> bonuses) throws SQLException {
        insert(conn, "GD_ITEM_SKILLS", "GD_ITEM_SKILL_LEVEL", id, bonuses);
    }

    public static void insertItemSet(final Connection conn, final String id, final List<DBSkillBonus> bonuses) throws SQLException {
        insert(conn, "GD_ITEMSET_SKILLS", "GD_ITEMSET_SKILL_LEVEL", id, bonuses);
    }

    public static void insertAffix(final Connection conn, final String id, final List<DBSkillBonus> bonuses) throws SQLException {
        insert(conn, "GD_AFFIX_SKILLS", "GD_AFFIX_SKILL_LEVEL", id, bonuses);
    }

    private static void insert(final Connection conn, final String tabName, final String tabNameLevel, final String id, final List<DBSkillBonus> bonuses) throws SQLException {
        final String insert = "INSERT INTO " + tabName + " VALUES (?,?,?,?,?,?)";
        if (bonuses == null) {
            return;
        }
        if (bonuses.isEmpty()) {
            return;
        }
        try (final PreparedStatement ps = conn.prepareStatement(insert)) {
            for (final DBSkillBonus bonus : bonuses) {
                if (bonus.bonusEntity == null && bonus.type != 4) {
                    continue;
                }
                ps.setString(1, id);
                ps.setInt(2, bonus.type);
                ps.setInt(3, bonus.index);
                ps.setString(4, bonus.bonusEntity);
                ps.setString(5, bonus.entityName);
                ps.setString(6, bonus.masteryID);
                ps.executeUpdate();
                ps.clearParameters();
                DBSkillBonusLevel.insert(conn, tabNameLevel, id, bonus);
            }
            ps.close();
        }
    }

    public static List<DBSkillBonus> getItem(final String id) throws SQLException {
        return get("GD_ITEM_SKILLS", "GD_ITEM_SKILL_LEVEL", "ITEM_ID", id);
    }

    public static List<DBSkillBonus> getItemSet(final String id) throws SQLException {
        return get("GD_ITEMSET_SKILLS", "GD_ITEMSET_SKILL_LEVEL", "ITEMSET_ID", id);
    }

    public static List<DBSkillBonus> getAffix(final String id) throws SQLException {
        return get("GD_AFFIX_SKILLS", "GD_AFFIX_SKILL_LEVEL", "AFFIX_ID", id);
    }

    private static List<DBSkillBonus> get(final String tableName, final String tableNameLevel, final String fieldName, final String id) {
        List<DBSkillBonus> list = new LinkedList<DBSkillBonus>();
        final String command = "SELECT * FROM " + tableName + " WHERE " + fieldName + " = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, id);
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs, tableNameLevel, fieldName);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {id, tableName};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    public static List<DBSkillBonus> getAllItem() throws SQLException {
        return getAll("GD_ITEM_SKILLS", "GD_ITEM_SKILL_LEVEL", "ITEM_ID");
    }

    public static List<DBSkillBonus> getAllAffix() throws SQLException {
        return getAll("GD_AFFIX_SKILLS", "GD_AFFIX_SKILL_LEVEL", "AFFIX_ID");
    }

    private static List<DBSkillBonus> getAll(final String tableName, final String tableNameLevel, final String fieldName) {
        List<DBSkillBonus> list = new LinkedList<DBSkillBonus>();
        final String command = "SELECT * FROM " + tableName;
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs, tableNameLevel, fieldName);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {"*", tableName};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    private static List<DBSkillBonus> wrap(final ResultSet rs, final String tableNameLevel, final String fieldName) throws SQLException {
        final LinkedList<DBSkillBonus> list = new LinkedList<DBSkillBonus>();
        while (rs.next()) {
            final DBSkillBonus bonus = new DBSkillBonus();
            bonus.itemID = rs.getString(1);
            bonus.type = rs.getInt(2);
            bonus.index = rs.getInt(3);
            bonus.bonusEntity = rs.getString(4);
            bonus.entityName = rs.getString(5);
            bonus.masteryID = rs.getString(6);
            bonus.levels = DBSkillBonusLevel.get(tableNameLevel, fieldName, bonus);
            bonus.currBonus = bonus.getValueForLevel(0);
            if (bonus.type == 2 && bonus.masteryID != null) {
                final DBSkill mastery = DBSkill.get(bonus.masteryID);
                if (mastery != null) {
                    bonus.masteryName = mastery.getName();
                }
            }
            list.add(bonus);
        }
        return list;
    }

    public static void updateDB() {
        updateItemDB();
        updateItemSetDB();
        updateAffixDB();
    }

    private static void updateItemDB() {
        updateDB("GD_ITEM_SKILLS", "GD_ITEM_SKILL_LEVEL", "ITEM_ID");
    }

    private static void updateItemSetDB() {
        updateDB("GD_ITEMSET_SKILLS", "GD_ITEMSET_SKILL_LEVEL", "ITEMSET_ID");
    }

    private static void updateAffixDB() {
        updateDB("GD_AFFIX_SKILLS", "GD_AFFIX_SKILL_LEVEL", "AFFIX_ID");
    }

    private static void updateDB(final String tableName, final String tableNameLevel, final String fieldName) {
        final List<DBSkillBonus> list = getEmptyNames(tableName, tableNameLevel, fieldName);
        if (list == null) {
            return;
        }
        for (final DBSkillBonus bonus : list) {
            if (bonus.bonusEntity == null) {
                continue;
            }
            final DBSkill skill = DBSkill.get(bonus.bonusEntity);
            if (skill == null) {
                final Object[] args = {tableName, bonus.itemID, bonus.bonusEntity, "GD_SKILL"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_REF_TABLE_ID_FROM_ID", args);
                GDMsgLogger.addWarning(msg);
            } else {
                bonus.entityName = skill.getName();
                bonus.masteryID = skill.getMasteryID();
            }
        }
        updateNames(tableName, fieldName, list);
    }

    private static List<DBSkillBonus> getEmptyNames(final String tableName, final String tableNameLevel, final String fieldName) {
        List<DBSkillBonus> list = new LinkedList<DBSkillBonus>();
        final String command = "SELECT * FROM " + tableName + " WHERE ENTITY_NAME is null or MASTERY_ID is null";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs, tableNameLevel, fieldName);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {"ENTITY_NAME is null", tableName};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    private static void updateNames(final String tableName, final String fieldName, final List<DBSkillBonus> list) {
        final String command = "UPDATE " + tableName + " SET ENTITY_NAME = ?, MASTERY_ID = ? WHERE " + fieldName + " = ? AND TYPE = ? AND INDEX = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            for (final DBSkillBonus bonus : list) {
                if (bonus.entityName == null && bonus.masteryID == null) {
                    continue;
                }
                ps.setString(1, bonus.entityName);
                ps.setString(2, bonus.masteryID);
                ps.setString(3, bonus.itemID);
                ps.setInt(4, bonus.type);
                ps.setInt(5, bonus.index);
                ps.executeUpdate();
                ps.clearParameters();
            }
            ps.close();
            conn.commit();
        } catch (SQLException ex) {
            final Object[] args = {"-", tableName};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex);
        }
    }

    public static void add(final List<DBSkillBonus> list, final List<DBSkillBonus> addList) {
        if (addList == null) {
            return;
        }
        for (DBSkillBonus bonusAdd : addList) {
            if (bonusAdd == null) {
                continue;
            }
            if (bonusAdd.bonusEntity == null) {
                continue;
            }
            bonusAdd = (DBSkillBonus) bonusAdd.clone();
            boolean found = false;
            for (final DBSkillBonus bonus : list) {
                if (bonus == null) {
                    continue;
                }
                if (bonusAdd.type == bonus.type && bonusAdd.bonusEntity.equals(bonus.bonusEntity)) {
                    bonus.setValue(bonus.getValue() + bonusAdd.getValue());
                    found = true;
                    break;
                }
            }
            if (found) {
                continue;
            }
            list.add(bonusAdd);
        }
    }

    public BonusDetail getBonusDetail() {
        final BonusDetail bonus = new BonusDetail(this);
        return bonus;
    }

    public BonusDetail getBonusDetail(final DBStashItem item) {
        final BonusDetail bonus = new BonusDetail(item, this);
        return bonus;
    }
}
